package com.dudy.authdemo.config;

import com.dudy.authdemo.service.impl.JwtUserDetailServiceImpl;
import com.dudy.common.base.CommonResponse;
import com.dudy.common.base.ResultCode;
import com.dudy.common.util.ResponseUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.util.DigestUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @program: auth
 * @author: dudy
 * @create: 2020-12-15 09:15
 * @description:
 **/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);

    @Autowired
    private JWTAuthenticationFilter jwtAuthenticationFilter;
    @Autowired
    private JwtUserDetailServiceImpl jwtUserDetailService;

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider
                = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(jwtUserDetailService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        // 配置自定义的校验器
        auth.authenticationProvider(authenticationProvider());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                if (rawPassword == null) {
                    throw new IllegalArgumentException("rawPassword cannot be null");
                }
                return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                if (rawPassword == null) {
                    throw new IllegalArgumentException("rawPassword cannot be null");
                }

                if (encodedPassword == null || encodedPassword.length() == 0) {
                    log.warn("Empty encoded password");
                    return false;
                }
                // 从密码中解析出盐值
                String[] s = encodedPassword.split("_");
                encodedPassword = s[0];
                String salt = "";
                if (s.length > 1) {
                    salt = s[1];
                }
                return encodedPassword.equals(DigestUtils.md5DigestAsHex((rawPassword.toString() + "_" + salt).getBytes()));
            }
        };
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.ignoring().antMatchers("/**/*js", "/**/*.css", "/**/html", "/**/htm", "/**/*.jpeg", "/**/*.png", "/**/*" +
                ".ico", "/static/**");
    }
    /**
     * 基于前后端不分类的情况
     * todo 没有验证过
     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        http.authorizeRequests()
//                .anyRequest().authenticated()
////                 and表示一个标签的结束
//                .and()
//                .formLogin()
////                 loginPage设置登陆界面
//                .loginPage("/login")
////                 设置登陆请求，如果不设置，默认为loginPage
//                .loginProcessingUrl("/login")
//                // 设置页面请求的参数，默认为username/password
//                .usernameParameter("username")
//                .passwordParameter("password")
//                // 配置登陆成功后跳转到index页面，如果先请求hello，然后登陆后就跳转到hello
//                .defaultSuccessUrl("/index")
//                // 等同于successForwardUrl
////                .defaultSuccessUrl("/index",true)
//                // 不管defaultSuccessUrl中的哪种情况，一律跳转到index，只需要配置一个即可
////                .successForwardUrl("/index")
////                 表示formLogin配置的内容不拦截
//                .permitAll()
//                .and()
//                // 配置logout
//                .logout()
//                // 修改登出的默认URL
//                .logoutUrl("/logout")
//                // 修改登出的url，以及登出请求方式，与logoutUrl功能相同
//                // .logoutRequestMatcher(new AntPathRequestMatcher("/logout", RequestMethod.POST.name()))
//                // 注销成功后跳转页面
//                .logoutSuccessUrl("/login")
//                // 清除cookie
//                .deleteCookies()
//                // 失效HttpSession
//                .invalidateHttpSession(true)
//                // 清除认证信息
//                .clearAuthentication(true)
//                .permitAll()
//                .and()
//                .csrf().disable();
//    }

    /**
     * 基于前后端分离，后段返回json给前端的情况
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 配置跨域
                .cors().configurationSource(corsConfigurationSource())
                .and()
//                 添加JWT过滤器 除已配置的其它请求都需经过此过滤器
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers("/login","/regist").permitAll()
                .and()
                .authorizeRequests()
                .anyRequest().authenticated()
//                .anyRequest().permitAll()
                .and()
                .formLogin()
                .permitAll()
                // 跳转成功返回json
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                                        Authentication authentication) throws IOException,
                            ServletException {
                        // 获取jwt token，返回给前端
                        User principal = (User) authentication.getPrincipal();
                        JwtTokenUtil jwtTokenUtil = new JwtTokenUtil();
                        String token = jwtTokenUtil.generateToken(principal.getUsername());
                        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                        PrintWriter writer = response.getWriter();
                        // 将登陆信息写回到前端
                        writer.write(new ObjectMapper().writeValueAsString(ResponseUtils.getResponse(token)));
                        writer.flush();
                        writer.close();
                    }
                })
                // 登陆失败跳转json
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                                        AuthenticationException exception) throws IOException,
                            ServletException {
                        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                        PrintWriter writer = response.getWriter();
                        // 将登陆信息写回到前端
                        writer.write(new ObjectMapper().writeValueAsString(defineLogoutMsg(exception)));
                        writer.flush();
                        writer.close();
                    }
                })
                .loginProcessingUrl("/login")
                .usernameParameter("username")
                .passwordParameter("password")
                .and()
                .logout()
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                                Authentication authentication) throws IOException, ServletException {
                        // 注销返回json的方法在这里，但是在前后端分离的场景下，不需要调用后端服务，即可注销登陆状态
                    }
                })
                .and()
                .csrf().disable().exceptionHandling()
                // 遇到未登录的请求，返回401给前端，让前端自行跳转
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response,
                                         AuthenticationException authException) throws IOException, ServletException {
                        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                        response.setStatus(403);
                        PrintWriter writer = response.getWriter();
                        // 将登陆信息写回到前端
                        writer.write(new ObjectMapper().writeValueAsString(defineLogoutMsg(authException)));
                        writer.flush();
                        writer.close();
                    }
                })
                .and()
                .rememberMe()
                .rememberMeParameter("rememberMe")
                .tokenValiditySeconds(7 * 24 * 60 * 60 * 1000)
                .and()
                // 所有的rest服务一定要设置为无状态，以提升操作效率和性能
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                // 最大会话数为1，表示后面登陆的会自动踢掉前面登陆的
                .maximumSessions(1)
        // 如果已经登陆，则禁止新的登陆，也就是别人登陆了，你就无法在登陆
//                .maxSessionsPreventsLogin(true)
        ;

    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    /**
     * 配置跨域
     *
     * @return
     */
    private CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");    //同源配置，*表示任何请求都视为同源，若需指定ip和端口可以改为如“localhost：8080”，多个以“，”分隔；
        corsConfiguration.addAllowedHeader("*");//header，允许哪些header，本案中使用的是token，此处可将*替换为token；
        corsConfiguration.addAllowedMethod("*");    //允许的请求方法，PSOT、GET等
        corsConfiguration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }


    private CommonResponse defineLogoutMsg(AuthenticationException exception) {
        String message = exception.getMessage();
        if (exception instanceof LockedException) {
            return ResponseUtils.getResponse(ResultCode.LOGIN_LOCK);
        }
        if (exception instanceof CredentialsExpiredException) {
            return ResponseUtils.getResponse(ResultCode.PWD_EXPIRE);
        }
        if (exception instanceof AccountExpiredException) {
            return ResponseUtils.getResponse(ResultCode.ACCOUNT_EXPIRE);
        }
        if (exception instanceof DisabledException) {
            return ResponseUtils.getResponse(ResultCode.ACCOUNT_DISABLE);
        }
        if (exception instanceof BadCredentialsException) {
            return ResponseUtils.getResponse(ResultCode.LOGIN_INFO_ERROR);
        }
        return ResponseUtils.getResponse(ResultCode.FAIL.getCode(), StringUtils.isBlank(message) ?
                ResultCode.FAIL.getMassage() : message);
    }
}
